//**********************************************************************
//	Programmer	-	Sujal Bista
//	File Name	-	model.h
//	Description	:	
//					3d model handler
//
//**********************************************************************

#ifndef _MODEL_H
#define _MODEL_H

#pragma once

#include "global.h"
#include "dataTypes.h"
#include "graphicsUtility.h"
#include "bucketList.h"
#include "camera.h"
#include "modelQuick.h"
class model
{
		#define MAX_FILE_NAME 128
		#define MAX_NAME 128
		#define MAXIMUM_MATRIX_COUNT 12
		#define MAXIMUM_BONE_PER_VERTEX_INFLUENCE_COUNT 4

	public:

		//mesh data
		class mesh
		{
			public:
				//vertex data
				class vertex:public modelQuick::meshData::vertexData
				{
					public:
				};
				//each face can have separate material
				class face:public modelQuick::meshData::faceData
				{
					public:

						//constructor
						face(void)
						{
							materialIndex=0;
						}
						//destructor
						~face()
						{
							
						}
				};
				//material data
				class material:public modelQuick::meshData::materialData
				{
					public:
						
						//clears the material
						material(void)
						{
							textureSRV=NULL;
							textureSampler=NULL;
							fileName[0]='\0';
						}

						//copy constructor
						material(const material& data)
						{
							diffuse=data.diffuse;  //diffuse color	
							ambient=data.ambient;  //ambient color
							specular=data.specular; //specular color
							emissive=data.emissive; //emissive color
							power=data.power;	//specular highlight power	
							strncpy(fileName,data.fileName,MAX_FILE_NAME); //copy filename
							fileName[MAX_FILE_NAME-1]=NULL;//put null at the end
							textureSRV=NULL;
							textureSampler=NULL;
						}

						//assignment operator
						material& operator=(const material& data)
						{
							if(this!=&data)
							{
								clear();
								diffuse=data.diffuse;  //diffuse color	
								ambient=data.ambient;  //ambient color
								specular=data.specular; //specular color
								emissive=data.emissive; //emissive color
								power=data.power;	//specular highlight power			
								strncpy(fileName,data.fileName,MAX_FILE_NAME); //copy filename
								fileName[MAX_FILE_NAME-1]=NULL;//put null at the end
								textureSRV=NULL;
								textureSampler=NULL;
							}
							return *this;
						}

						//clear the data
						void clear(void)
						{
							delete textureSRV;
							delete textureSampler;

							textureSRV=NULL;
							textureSampler=NULL;
						}

						~material()
						{
							clear();
						}
				};

				char name[MAX_NAME];
				unsigned int vertexCount;
				unsigned int faceCount;				
				unsigned int materialCount;
				vertex *vertices;
				face *faces;
				material *materials;

			public:
				mesh(void)
				{
					name[0]=NULL;
					vertices=NULL;
					faces=NULL;
					materials=NULL;
					
					faceCount=0;
					vertexCount=0;
					materialCount=0;
				}

				//copy constructor
				mesh(const mesh &data)
				{

					faceCount=data.faceCount;
					vertexCount=data.vertexCount;
					materialCount=data.materialCount;
					if(data.name)
					{
						strncpy(name,data.name,MAX_NAME);
						name[MAX_NAME-1]=NULL;
					}
					else
						name[0]=NULL;

					//copy face
					if(faceCount)
					{
						faces=new face[faceCount];
						for(unsigned int i=0;i<faceCount;i++)
							faces[i]=data.faces[i];
					}
					else
						faces=NULL;

					//copy vertices
					if(vertexCount)
					{
						vertices=new vertex[vertexCount];
						for(unsigned int i=0;i<vertexCount;i++)
							vertices[i]=data.vertices[i];
					}
					else
						vertices=NULL;

					//copy materials
					if(materialCount)
					{
						materials=new material[materialCount];
						for(unsigned int i=0;i<materialCount;i++)
							materials[i]=data.materials[i];
					}
					else
						materials=NULL;
				}

				//copy constructor
				mesh& operator=(const mesh& data)
				{
					if(this!=&data)
					{
						clear();

						faceCount=data.faceCount;
						vertexCount=data.vertexCount;
						materialCount=data.materialCount;

						if(data.name)
						{
							strncpy(name,data.name,MAX_NAME);
							name[MAX_NAME-1]=NULL;
						}
						else
							name[0]=NULL;
												
						//copy vertices
						if(vertexCount)
						{
							vertices=new vertex[vertexCount];
							for(unsigned int i=0;i<vertexCount;i++)
								vertices[i]=data.vertices[i];
						}
						else
							vertices=NULL;

						//copy face
						if(faceCount)
						{
							faces=new face[faceCount];
							for(unsigned int i=0;i<faceCount;i++)
								faces[i]=data.faces[i];
						}
						else
							faces=NULL;

						//copy materials
						if(materialCount)
						{
							materials=new material[materialCount];
							for(unsigned int i=0;i<materialCount;i++)
								materials[i]=data.materials[i];
						}
						else
							materials=NULL;
					}
					return *this;
				}

				void clear(void)
				{
					delete[] vertices;
					delete[] faces;
					delete[] materials;
					
					name[0]=NULL;
					vertices=NULL;
					faces=NULL;
					materials=NULL;

					faceCount=0;
					vertexCount=0;
					materialCount=0;
				}

				~mesh()
				{
					clear();
				}
		};
		//animation sets
		class animation
		{
			public:
				//per bone animation data
				class boneAnimation:public modelQuick::animationData::boneAnimationData
				{
					public:

						boneAnimation(void)
						{
							rotationCount=0;
							scaleCount=0;
							translationCount=0;
							name[0]=NULL;
							rotations=NULL;
							scales=NULL;
							translations=NULL;
						}

						void clear(void)
						{
							delete[] rotations;
							delete[] scales;
							delete[] translations;

							rotationCount=0;
							scaleCount=0;
							translationCount=0;
							name[0]=NULL;
							rotations=NULL;
							scales=NULL;
							translations=NULL;
						}

						~boneAnimation()
						{
							clear();
						}
				};
				
				char name[MAX_NAME];

				//number of 
				unsigned int boneAnimationCount;
				//number of ticks per second
				float ticksPerSecond;
				//bone Animation data
				boneAnimation *boneAnimations;
								
				animation(void)
				{
					boneAnimations=NULL;
					boneAnimationCount=0;
					name[0]=NULL;
					ticksPerSecond=4800.0f;
				}

				void clear(void)
				{
					delete[] boneAnimations;
					boneAnimations=NULL;
					name[0]=NULL;
					boneAnimationCount=0;					
				}

				~animation()
				{
					clear();
				}
		};				
		//contains information about skinning
		class skin
		{
			public:
				//bone data
				class bone:public modelQuick::skinData::boneData
				{
					public:
						bone(void)
						{
							name[0]=NULL;
							vertexIndex=NULL;
							weights=NULL;
							influenceCount=0;
							boneHierarchyIndex=(unsigned int)-1;
						}

						//copy constructor
						bone(const bone &data)
						{

							if(data.name)
							{
								strncpy(name,data.name,MAX_NAME);
								name[MAX_NAME-1]=NULL;
							}
							else
								name[0]=NULL;

							offsetMatrix=data.offsetMatrix;
							influenceCount=data.influenceCount;
							
							if(influenceCount)
							{
								weights=new float[influenceCount];							
								vertexIndex=new unsigned int[influenceCount];
								memcpy(vertexIndex,data.vertexIndex,sizeof(unsigned int)*influenceCount);
								memcpy(weights,data.weights,sizeof(float)*influenceCount);
							}
							else
							{
								vertexIndex=NULL;
								weights=NULL;
							}

							boneHierarchyIndex=data.boneHierarchyIndex;
						}

						//copy constructor
						bone& operator=(const bone& data)
						{
							if(this!=&data)
							{		
								clear();
								if(data.name)
								{
									strncpy(name,data.name,MAX_NAME);
									name[MAX_NAME-1]=NULL;
								}
								else
									name[0]=NULL;

								offsetMatrix=data.offsetMatrix;
								influenceCount=data.influenceCount;
								
								if(influenceCount)
								{
									weights=new float[influenceCount];							
									vertexIndex=new unsigned int[influenceCount];
									memcpy(vertexIndex,data.vertexIndex,sizeof(unsigned int)*influenceCount);
									memcpy(weights,data.weights,sizeof(float)*influenceCount);
								}
								else
								{
									vertexIndex=NULL;
									weights=NULL;
								}

								boneHierarchyIndex=data.boneHierarchyIndex;
							}
							return *this;
						}

						void clear(void)
						{
							delete[] vertexIndex;
							delete[] weights;
							
							name[0]=NULL;
							vertexIndex=NULL;
							weights=NULL;
							boneHierarchyIndex=(unsigned int)-1;
							influenceCount=0;
						}

						~bone()
						{
							clear();
						}

				};

				//bone hierarchy
				class boneHierarchy:public modelQuick::skinData::boneHierarchyData
				{
					public:

						boneHierarchy(void)
						{
							name[0]=NULL;
							siblingIndex=(unsigned int)-1;
							childIndex=(unsigned int)-1;
							boneIndex=(unsigned int)-1;
							boneHierarchyIndex=(unsigned int)-1;
						}

						//copy constructor
						boneHierarchy(const boneHierarchy &data)
						{
							//allocate space for the name and copy the data
							if(data.name)
							{
								strncpy(name,data.name,MAX_NAME);
								name[MAX_NAME-1]=NULL;
							}
							else
								name[0]=NULL;
							
							transformationMatrix=data.transformationMatrix;
							boneIndex=data.boneIndex;
							boneHierarchyIndex=data.boneHierarchyIndex;

							siblingIndex=data.siblingIndex;
							childIndex=data.childIndex;
						}

						//assignment operator
						boneHierarchy& operator=(const boneHierarchy &data)
						{
							if(this!=&data)
							{
								clear();

								//allocate space for the name and copy the data
								if(data.name)
								{
									strncpy(name,data.name,MAX_NAME);
									name[MAX_NAME-1]=NULL;
								}
								else
									name[0]=NULL;

								transformationMatrix=data.transformationMatrix;
								boneIndex=data.boneIndex;
								boneHierarchyIndex=data.boneHierarchyIndex;

								siblingIndex=data.siblingIndex;
								childIndex=data.childIndex;
								
							}
							return *this;
						}

						//traverses breadth first
						void traverseBFS(boneHierarchy *bonesHierarchy, bucketList<boneHierarchy *> &tree)
						{
							//add the data to the list
							tree.addBack(this);
							if(siblingIndex!=(unsigned int)-1)
								bonesHierarchy[siblingIndex].traverseBFS(bonesHierarchy,tree);
							if(childIndex!=(unsigned int)-1)
								bonesHierarchy[childIndex].traverseBFS(bonesHierarchy,tree);
						}

						void clear(void)
						{
							name[0]=NULL;
							boneIndex=(unsigned int)-1;
							siblingIndex=(unsigned int)-1;
							childIndex=(unsigned int)-1;
						}

						~boneHierarchy()
						{
							clear();
						}


				};

				//number of bones
				unsigned int boneCount;
				//number of boneHierarchy
				unsigned int boneHierarchyCount;
				
				bone *bones;
				boneHierarchy *bonesHierarchy;
				//boneHierarchy *rootBone;
				unsigned int rootIndex;

			private:

				//searches for a bone from the list
				unsigned int findBone(const char *name)
				{
					if(name)
					{
						for(unsigned int i=0;i<boneCount;i++)
						{
							if(bones[i].name && strcmp(bones[i].name,name)==0)
								return i;
						}
					}
					return (unsigned int)-1;
				}

			public:

				skin(void)
				{
					bones=NULL;
					rootIndex=(unsigned int)-1;
					bonesHierarchy=NULL;
					boneCount=0;
					boneHierarchyCount=0;
				}

				//copy constructor
				skin(const skin &data)
				{
					bones=NULL;
					rootIndex=(unsigned int)-1;
					bonesHierarchy=NULL;
					boneCount=data.boneCount;
					boneHierarchyCount=data.boneHierarchyCount;
					if(boneCount)
					{
						bones=new bone[boneCount];
						//copy bone
						for(unsigned int i=0;i<boneCount;i++)
							bones[i]=data.bones[i];
					}
					if(boneHierarchyCount)
					{
						bonesHierarchy=new boneHierarchy[boneHierarchyCount];
						for(unsigned int i=0;i<boneHierarchyCount;i++)
							bonesHierarchy[i]=data.bonesHierarchy[i];

						//rootBone=bonesHierarchy;
						rootIndex=data.rootIndex;
					}
				}

				//assignment operator
				skin& operator=(const skin& data)
				{
					if(this!=&data)
					{
						clear();

						boneCount=data.boneCount;
						boneHierarchyCount=data.boneHierarchyCount;
						if(boneCount)
						{
							bones=new bone[boneCount];
							//copy bone
							for(unsigned int i=0;i<boneCount;i++)
								bones[i]=data.bones[i];
						}
						if(boneHierarchyCount)
						{
							bonesHierarchy=new boneHierarchy[boneHierarchyCount];
							for(unsigned int i=0;i<boneHierarchyCount;i++)
								bonesHierarchy[i]=data.bonesHierarchy[i];
							rootIndex=data.rootIndex;
						}
					}
					return *this;
				}

				//initializes the skin
				void initialize(void)
				{
					for(unsigned int i=0;i<boneHierarchyCount;i++)
					{
						bonesHierarchy[i].boneHierarchyIndex=i;
						bonesHierarchy[i].boneIndex=findBone(bonesHierarchy[i].name);
						if(bonesHierarchy[i].boneIndex!=(unsigned int)-1)
							bones[bonesHierarchy[i].boneIndex].boneHierarchyIndex=i;
					}
				}

				//searches for a bone from the hierarchy list
				unsigned int findHierarchyBone(const char *name)
				{
					if(name)
					{
						for(unsigned int i=0;i<boneHierarchyCount;i++)
						{
							if(bonesHierarchy[i].name && strcmp(bonesHierarchy[i].name,name)==0)
								return i;
						}
					}
					return (unsigned int)-1;
				}
				
				void clear(void)
				{
					delete[] bones;
					delete[] bonesHierarchy;
					
					bones=NULL;
					rootIndex=(unsigned int)-1;
					bonesHierarchy=NULL;
						
					boneCount=0;
					boneHierarchyCount=0;
				}
				~skin()
				{
					clear();
				}
		};
		//different instances of the mesh
		typedef modelQuick::instanceData instance;

		//data for the static structure
		struct meshHeader
		{
			//number of meshes
			unsigned int meshCount;
			//number of animations
			unsigned int animationCount;
			//number of skins
			unsigned int skinCount;		
		
			//all the meshes
			mesh *meshes;
			//animations
			animation *animations;
			//skins
			skin *skins;

		}staticMeshHeader;
		
		//different instances of the mesh
		bucketList<instance> instances;

	public:
	
		//constructor
		model(void);

		//init
		bool init(graphicsUtility &glu);
		//updates the mesh
		void update(const float &dt);
		//show the mesh
		void show(graphicsUtility &glu,const camera &eye,const bool useTexture=true);

		//free the data
		void clear(void);
		//destructor
		~model();

		//get total number of active faces
		//includes all the instances
		unsigned int getActiveFacesSize(void);

		//write the file
		bool write(char *fileName);
};

#endif
